import java.util.Stack;

public class p1 {

	LexicalAnalyzer lx;
	Stack<ASTTreeNode> stack = new Stack<ASTTreeNode>();
	Token nextToken;
	
	
	public p1(String fileName){
		// Read NOOUT
		lx = new LexicalAnalyzer(fileName);
		// System.out.println(parser.lx.inputProg);
		nextToken = lx.getNextToken();
		E();
		
		//print ast
		//stack.peek().print(0);
		
	}
	
	public ASTTreeNode getASTTree(){
		return stack.pop();
	}

	public p1() {
		
	}

	public static void main(String[] args) {
		p1 parser = new p1();
		if (args[0].compareTo("-ast") == 0) {
			// Read NOOUT
			String noout = args[1];
			parser.lx = new LexicalAnalyzer(args[2]);
			// System.out.println(parser.lx.inputProg);
			parser.nextToken = parser.lx.getNextToken();
			parser.E();
			parser.stack.pop().print(0);
		}else if (args[0].compareTo("-l") == 0) {
			parser.lx = new LexicalAnalyzer(args[1]);
			System.out.println(parser.lx.inputProg);
		}
	}

	private void buildTree(String string, int n, boolean terminal) {
		ASTTreeNode temp[] = new ASTTreeNode[n];
		for (int i = (n - 1); i >= 0; i--) {
			temp[i] = stack.pop();
		}
		ASTTreeNode newNode = new ASTTreeNode(string, n);
		newNode.terminal = terminal;
		for (int i = 0; i < n; i++) {
			newNode.assignChild(i, temp[i]);
		}
		stack.push(newNode);
	}
	
	private void buildTree(String string, int n) {
		buildTree(string, n, false);
	}

	private void E() {
		if (nextToken.content.compareTo("let") == 0) {
			nextToken = lx.getNextToken();
			D();
			check("in");
			nextToken = lx.getNextToken();
			E();
			buildTree("let", 2);
		} else if (nextToken.content.compareTo("fn") == 0) {
			nextToken = lx.getNextToken();
			int n = 0;
			Vb();
			n++;
			while (nextToken.content.compareTo(".") != 0) {
				Vb();
				n++;
			}
			check(".");
			nextToken = lx.getNextToken();
			E();
			buildTree("lambda", n + 1);
		} else {
			Ew();
		}
	}

	private void Vb() {
		if (nextToken.type == 0) {
			// build tree can go up here since no children nodes are created
			boolean terminal = true;
			buildTree("<ID:" + nextToken.content + ">", 0, terminal);
			nextToken = lx.getNextToken();
		} else if (nextToken.content.compareTo("(") == 0) {
			// build tree can go up here since no children nodes are created
			nextToken = lx.getNextToken();
			if (nextToken.content.compareTo(")") == 0) {
				// case: Vb -> '('')' :: consuming ')'
				nextToken = lx.getNextToken();
				buildTree("<()>", 0);
			} else {
				Vl();
				// consuming ')'
				check(")");
				nextToken = lx.getNextToken();
			}
		}
	}

	private void Vl() {
		int n = 1;
		boolean terminal = true;
		buildTree("<ID:" + nextToken.content + ">", 0, terminal);
		nextToken = lx.getNextToken();
		while (nextToken.content.compareTo(",") == 0) {
			nextToken = lx.getNextToken();
			// consume the comming IDentifier
			checkIsID();
			buildTree("<ID:" + nextToken.content + ">", 0, terminal);
			nextToken = lx.getNextToken();
			n++;
		}
		if (n > 1) {
			buildTree(",", n);
		}
	}

	private void D() {
		Da();
		if (nextToken.content.compareTo("within") == 0) {
			nextToken = lx.getNextToken();
			D();
			buildTree("within", 2);
		}
	}

	private void Da() {
		Dr();
		int n = 1;
		while (nextToken.content.compareTo("and") == 0) {
			nextToken = lx.getNextToken();
			Dr();
			n++;
		}
		if (n > 1) {
			buildTree("and", n);
		}
	}

	private void Dr() {
		if (nextToken.content.compareTo("rec") == 0) {
			nextToken = lx.getNextToken();
			Db();
			buildTree("rec", 1);
		} else {
			Db();
		}
	}

	/*
	 * Handling Db needs LL(2) parsing,
	 */
	private void Db() {
		// using the third production
		if (nextToken.content.compareTo("(") == 0) {
			nextToken = lx.getNextToken();
			D();
			check(")");
			// consuming ")"
			nextToken = lx.getNextToken();
		} else if (nextToken.type == 0) {
			// consumed token1 here and remembering it
			Token token1 = nextToken;
			Token token2 = nextToken = lx.getNextToken();
			// If the second token is "," than Vl will be used
			if (token2.content.compareTo(",") == 0
					|| token2.content.compareTo("=") == 0) {
				Db_1(token1, token2);
			} else {
				// there is no "," node than we cant consume any comma
				Db_2(token1, token2);
			}
		}
	}

	private void Db_2(Token token1, Token token2) {
		// / here goes the function name
		boolean terminal = true;
		buildTree("<ID:" + token1.content + ">", 0, terminal);

		// we nned Vb+ here plus we need the number of parameters back from it
		int numParam = 0;
		// here goes the second
		Vb();
		numParam = 1;

		while (nextToken.content.compareTo("=") != 0) {
			Vb();
			numParam++;
		}

		// consume "="
		check("=");
		nextToken = lx.getNextToken();
		E();
		buildTree("function_form", numParam + 2);
	}

	/*
	 * This is Vb+ implementation, that is first has to be atleast once
	 */
	private int Vb_n() {
		return 0;
	}

	private void Db_1(Token token1, Token token2) {
		// just one variable declared
		if (token2.content.compareTo("=") == 0) {
			// push token1 and consume "=" token
			nextToken = lx.getNextToken();
			boolean terminal = true;
			buildTree("<ID:" + token1.content + ">", 0, terminal);
		} else if (token2.content.compareTo(",") == 0) {
			// consume the comma or token2
			nextToken = lx.getNextToken();
			// Vl handles <ID>'s that are comma separated
			Vl_LL2(token1, token2);

			// consume the "=" before calling E because "=" was consumed in the
			// if above
			check("=");
			nextToken = lx.getNextToken();

		} else {
			System.out.println("Error");
		}
		E();
		buildTree("=", 2);
	}

	private void Vl_LL2(Token token1, Token token2) {
		// <ID1=token1> , <ID2=nextToken> ,
		// add token1 to tree
		int n = 1;
		boolean terminal = true;
		buildTree("<ID:" + token1.content + ">", 0, terminal);

		checkIsID();
		n = 2;
		// consuming second <ID> which is comming for sure
		buildTree("<ID:" + nextToken.content + ">", 0, terminal);
		nextToken = lx.getNextToken();

		while (nextToken.content.compareTo(",") == 0) {
			nextToken = lx.getNextToken();
			// consume the comming IDentifier
			checkIsID();
			buildTree("<ID:" + nextToken.content + ">", 0, terminal);
			nextToken = lx.getNextToken();
			n++;
		}
		if (n > 1) {
			buildTree(",", n);
		}
	}

	private void Ew() {
		T();
		if (nextToken.content.compareTo("where") == 0) {
			nextToken = lx.getNextToken();
			Dr();
			buildTree("where", 2);
		}
	}

	private void T() {
		Ta();
		int n = 1;
		while (nextToken.content.compareTo(",") == 0) {
			nextToken = lx.getNextToken();
			Ta();
			n++;
		}
		if (n > 1) {
			buildTree("tau", n);
		}
	}

	private void Ta() {
		Tc();
		while (nextToken.content.compareTo("aug") == 0) {
			nextToken = lx.getNextToken();
			Tc();
			// build tau tree;
			buildTree("aug", 2);
		}
	}

	private void Tc() {
		B();
		if (nextToken.content.compareTo("->") == 0) {
			nextToken = lx.getNextToken();
			Tc();
			check("|");
			nextToken = lx.getNextToken();
			Tc();
			// build tau tree;
			buildTree("->", 3);
		}
	}

	private void B() {
		Bt();
		while (nextToken.content.compareTo("or") == 0) {
			nextToken = lx.getNextToken();
			Bt();
			buildTree("or", 2);
		}
	}

	private void Bt() {
		Bs();
		while (nextToken.content.compareTo("&") == 0) {
			nextToken = lx.getNextToken();
			Bs();
			buildTree("&", 2);
		}
	}

	private void Bs() {
		if (nextToken.content.compareTo("not") == 0) {
			nextToken = lx.getNextToken();
			Bp();
			// build tree
			buildTree("not", 1);
		} else {
			Bp();
		}
	}

	private void Bp() {
		A();
		while (nextToken.content.compareTo("gr") == 0
				| nextToken.content.compareTo(">") == 0
				| nextToken.content.compareTo("ge") == 0
				| nextToken.content.compareTo(">=") == 0
				| nextToken.content.compareTo("ls") == 0
				| nextToken.content.compareTo("<") == 0
				| nextToken.content.compareTo("le") == 0
				| nextToken.content.compareTo("<=") == 0
				| nextToken.content.compareTo("eq") == 0
				| nextToken.content.compareTo("ne") == 0) {
			A();
			if (nextToken.content.compareTo("gr") == 0
					| nextToken.content.compareTo(">") == 0) {
				nextToken = lx.getNextToken();
				A();
				// build tree gr
				buildTree("gr", 2);
			} else if (nextToken.content.compareTo("ge") == 0
					| nextToken.content.compareTo(">=") == 0) {
				nextToken = lx.getNextToken();
				A();
				// build tree ge
				buildTree("ge", 2);
			} else if (nextToken.content.compareTo("ls") == 0
					| nextToken.content.compareTo("<") == 0) {
				nextToken = lx.getNextToken();
				A();
				// build tree gr
				buildTree("ls", 2);
			} else if (nextToken.content.compareTo("le") == 0
					| nextToken.content.compareTo(">=") == 0) {
				nextToken = lx.getNextToken();
				A();
				// build tree gr
				buildTree("le", 2);
			} else if (nextToken.content.compareTo("eq") == 0) {
				nextToken = lx.getNextToken();
				A();
				// build tree gr
				buildTree("eq", 2);
			} else if (nextToken.content.compareTo("ne") == 0) {
				nextToken = lx.getNextToken();
				A();
				// build tree gr
				buildTree("ne", 2);
			}
		}
	}

	private void A() {
		if (nextToken.content.compareTo("+") == 0 && nextToken.type == 2) {
			nextToken = lx.getNextToken();
			At();
			// do not build tree gr
		} else if (nextToken.content.compareTo("-") == 0 && nextToken.type == 2) {
			nextToken = lx.getNextToken();
			At();
			// build tree gr
			buildTree("neg", 1);
		} else {
			At();
		}
		while (nextToken.content.compareTo("+") == 0
				| nextToken.content.compareTo("-") == 0) {
			if (nextToken.content.compareTo("+") == 0) {
				nextToken = lx.getNextToken();
				At();
				buildTree("+", 2);
			} else if (nextToken.content.compareTo("-") == 0) {
				nextToken = lx.getNextToken();
				At();
				// build tree neg
				buildTree("-", 2);
			}
		}

	}

	private void At() {
		Af();
		while (nextToken.content.compareTo("*") == 0
				| nextToken.content.compareTo("/") == 0) {
			if (nextToken.content.compareTo("*") == 0) {
				nextToken = lx.getNextToken();
				Af();
				// build tree gr
				buildTree("*", 2);
			} else if (nextToken.content.compareTo("/") == 0) {
				nextToken = lx.getNextToken();
				Af();
				// build tree
				buildTree("/", 2);
			}
		}
	}

	private void Af() {
		Ap();
		if (nextToken.content.compareTo("**") == 0) {
			nextToken = lx.getNextToken();
			Af();
			// build tree gr
			buildTree("**", 2);
		}
	}

	private void Ap() {
		R();
		boolean terminal = true;
		while (nextToken.content.compareTo("@") == 0) {
			// @ token gone
			nextToken = lx.getNextToken();
			checkIsID();
			buildTree("<ID:" + nextToken.content + ">", 0, terminal);
			// pickup ID2 token
			nextToken = lx.getNextToken();
			// call R
			R();
			// Build_tree
			buildTree("@", 3);
		}
	}

	private void R() {
		Rn();
		while (nextToken.type == 0 | nextToken.type == 1 | nextToken.type == 3
				| nextToken.content.compareTo("true") == 0
				| nextToken.content.compareTo("false") == 0
				| nextToken.content.compareTo("nil") == 0
				| nextToken.content.compareTo("(") == 0
				| nextToken.content.compareTo("dummy") == 0) {
			Rn();
			buildTree("gamma", 2);
		}
	}

	private void Rn() {
		// System.out.println(nextToken.content);
		Token tokenHolder;
		boolean terminal = true;
		if (nextToken.type == 0) {
			tokenHolder = nextToken;
			nextToken = lx.getNextToken();
			// buildTree(0)
			buildTree("<ID:" + tokenHolder.content + ">", 0, terminal);
		} else if (nextToken.type == 1) {
			tokenHolder = nextToken;
			nextToken = lx.getNextToken();
			// buildTree(0)
			buildTree("<INT:" + tokenHolder.content + ">", 0, terminal);
		} else if (nextToken.type == 3) {
			tokenHolder = nextToken;
			nextToken = lx.getNextToken();
			// buildTree(0)
			buildTree("<STR:'" + tokenHolder.content + "'>", 0, terminal);
		} else if (nextToken.content.compareTo("true") == 0) {
			nextToken = lx.getNextToken();
			// buildTree(0)
			buildTree("<true>", 0, terminal);
		} else if (nextToken.content.compareTo("false") == 0) {
			nextToken = lx.getNextToken();
			// buildTree(0)
			buildTree("<false>", 0, terminal);
		} else if (nextToken.content.compareTo("nil") == 0) {
			nextToken = lx.getNextToken();
			// buildTree(0)
			buildTree("<nil>", 0, terminal);
		} else if (nextToken.content.compareTo("(") == 0) {
			nextToken = lx.getNextToken();
			E();
			check(")");
			nextToken = lx.getNextToken();
			// no buildTree
		} else if (nextToken.content.compareTo("dummy") == 0) {
			nextToken = lx.getNextToken();
			// buildTree(0)
			buildTree("<dummy>", 0, terminal);
		} else if (nextToken.type == 8 | nextToken.type == 2) {
			// if it is a reserved keyword than pass
			;
		} else {
			// Error
			System.out
					.println("Error Rn(): this state should not be reached: Token: "
							+ nextToken);
		}
	}

	private void checkIsID() {
		if (nextToken.type != 0) {
			System.out
					.println("Error checkID: Expected a token to be a Identifier but found a "
							+ Token.tokenType[nextToken.type]
							+ " Token content: " + nextToken.content);
			System.exit(0);
		}
	}

	private void check(String string) {
		if (nextToken.content.compareTo(string) != 0) {
			System.out.println("Error check: Expected a token " + string
					+ " , but instead found " + nextToken.content);
			System.exit(0);
		}
	}
}
